

using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.Xna.Framework.Graphics;


namespace UnityEditor.Controls.GameGraphicsControl
{
    // System.Drawing and the XNA Framework both define Color and Rectangle
    // types. To avoid conflicts, we specify exactly which ones to use.
    using Color = System.Drawing.Color;
    using Rectangle = Microsoft.Xna.Framework.Rectangle;
    using UnityEditor.Layout;
    using UnityEngine.Core;
    using Microsoft.Xna.Framework;
    using UnityEditor.Controls.GraphicsControl;
    using System.Windows.Interop;


    /// <summary>
    /// Custom control uses the XNA Framework GraphicsDevice to render onto
    /// a Windows Form. Derived classes can override the Initialize and Draw
    /// methods to add their own drawing code.
    /// </summary>
    public class GraphicsDeviceControl : Control
    {
        public GraphicsDevice GraphicsDevice
        {
            get;
            set;
        }

        private GraphicsGame game;
        public GraphicsGame Game
        {
            get
            {
                return game;
            }
            set
            {
                game = value;
                if (game != null)
                    this.GraphicsDevice = game.GraphicsDevice;
            }
        }

        protected override void OnCreateControl()
        {
            // Don't initialize the graphics device if we are running in the designer.
            if (!DesignMode)
            {
                // Give derived classes a chance to initialize themselves.
                Initialize();
            }

            base.OnCreateControl();
        }


        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }


        /// <summary>
        /// Redraws the control in response to a WinForms paint message.
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            if (Workspace.Instance.GameStatus == GameStatus.PlayInWindow)
                return;

            string beginDrawError = BeginDraw();

            if (string.IsNullOrEmpty(beginDrawError))
            {
                // Draw the control using the GraphicsDevice.
                Draw();
                EndDraw();
            }
            else
            {
                // If BeginDraw failed, show an error message using System.Drawing.
                PaintUsingSystemDrawing(e.Graphics, beginDrawError);
            }
        }


        /// <summary>
        /// Attempts to begin drawing the control. Returns an error message string
        /// if this was not possible, which can happen if the graphics device is
        /// lost, or if we are running inside the Form designer.
        /// </summary>
        string BeginDraw()
        {
            if (GraphicsDevice == null)
                return "";

            // Make sure the graphics device is big enough, and is not lost.
            string deviceResetError = HandleDeviceReset();

            if (!string.IsNullOrEmpty(deviceResetError))
            {
                return deviceResetError;
            }

            // Many GraphicsDeviceControl instances can be sharing the same
            // GraphicsDevice. The device backbuffer will be resized to fit the
            // largest of these controls. But what if we are currently drawing
            // a smaller control? To avoid unwanted stretching, we set the
            // viewport to only use the top left portion of the full backbuffer.
            Viewport viewport = new Viewport();

            viewport.X = 0;
            viewport.Y = 0;

            viewport.Width = ClientSize.Width;
            viewport.Height = ClientSize.Height;

            viewport.MinDepth = 0;
            viewport.MaxDepth = 1;

            GraphicsDevice.Viewport = viewport;

            return null;
        }


        /// <summary>
        /// Ends drawing the control. This is called after derived classes
        /// have finished their Draw method, and is responsible for presenting
        /// the finished image onto the screen, using the appropriate WinForms
        /// control handle to make sure it shows up in the right place.
        /// </summary>
        void EndDraw()
        {
            try
            {
                Rectangle sourceRectangle = new Rectangle(0, 0, ClientSize.Width,
                                                                ClientSize.Height);

                GraphicsDevice.Present(sourceRectangle, null, this.Handle);
            }
            catch
            {
                // Present might throw if the device became lost while we were
                // drawing. The lost device will be handled by the next BeginDraw,
                // so we just swallow the exception.
            }
        }


        /// <summary>
        /// Helper used by BeginDraw. This checks the graphics device status,
        /// making sure it is big enough for drawing the current control, and
        /// that the device is not lost. Returns an error string if the device
        /// could not be reset.
        /// </summary>
        string HandleDeviceReset()
        {
            if (GraphicsDevice == null)
                return "";
            bool deviceNeedsReset = false;

            switch (GraphicsDevice.GraphicsDeviceStatus)
            {
                case GraphicsDeviceStatus.Lost:
                    // If the graphics device is lost, we cannot use it at all.
                    return "Graphics device lost";

                case GraphicsDeviceStatus.NotReset:
                    // If device is in the not-reset state, we should try to reset it.
                    deviceNeedsReset = true;
                    break;

                default:
                    // If the device state is ok, check whether it is big enough.
                    PresentationParameters pp = GraphicsDevice.PresentationParameters;

                    deviceNeedsReset = (ClientSize.Width > pp.BackBufferWidth) ||
                                       (ClientSize.Height > pp.BackBufferHeight);
                    break;
            }

            // Do we need to reset the device?
            if (deviceNeedsReset)
            {
                try
                {
                    Game.Resize((int)ClientSize.Width, (int)ClientSize.Height);
                    GraphicsHelper.CallGameMethod(Game, "Initialize", new object[] { });
                }
                catch (Exception e)
                {
                    return "Graphics device reset failed\n\n" + e;
                }
            }

            return null;
        }


        /// <summary>
        /// If we do not have a valid graphics device (for instance if the device
        /// is lost, or if we are running inside the Form designer), we must use
        /// regular System.Drawing method to display a status message.
        /// </summary>
        protected virtual void PaintUsingSystemDrawing(Graphics graphics, string text)
        {
            graphics.Clear(Color.CornflowerBlue);

            using (Brush brush = new SolidBrush(Color.Black))
            {
                using (StringFormat format = new StringFormat())
                {
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Center;

                    graphics.DrawString(text, Font, brush, ClientRectangle, format);
                }
            }
        }


        /// <summary>
        /// Ignores WinForms paint-background messages. The default implementation
        /// would clear the control to the current background color, causing
        /// flickering when our OnPaint implementation then immediately draws some
        /// other color over the top using the XNA Framework GraphicsDevice.
        /// </summary>
        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
        }

        protected void Initialize()
        {
        }

        public Rectangle ClipRectangle
        {
            get
            {
                Rectangle ret = new Rectangle();
                System.Drawing.Point leftTop0 = this.PointToScreen(new System.Drawing.Point(0, 0));
                System.Drawing.Point rightBottom0 = this.PointToScreen(new System.Drawing.Point(this.ClientSize.Width, this.ClientSize.Height));

                var leftTop = System.Windows.Application.Current.MainWindow.PointFromScreen(new System.Windows.Point(leftTop0.X, leftTop0.Y));
                var rightBottom = System.Windows.Application.Current.MainWindow.PointFromScreen(new System.Windows.Point(rightBottom0.X, rightBottom0.Y));

                ret = new Rectangle((int)leftTop.X, (int)leftTop.Y, (int)(rightBottom.X - leftTop.X), (int)(rightBottom.Y - leftTop.Y));
                return ret;
            }
        }

        // Game Graphics
        public void Draw()
        {
            if (Workspace.Instance.GameStatus == GameStatus.PlayInWindow)
                return;

            if (this.Game == null)
                return;

            Game.GameScene = Workspace.Instance.GameScene;
            Game.GameScene.Mode = SceneMode.NormalMode;
            this.Game.ClearBackground(Microsoft.Xna.Framework.Color.BurlyWood);
            Game.InputManager.ClipRectangle = this.ClipRectangle;

            Time.Tick();
            GameTime gameTime = new GameTime(Time.TotalTime, Time.DeltaTime);

            if (Workspace.Instance.GameStatus == GameStatus.Startup)
            {
                GraphicsHelper.CallGameMethod(Game, "Startup", new object[] { gameTime });
                Workspace.Instance.GameStatus = GameStatus.Play;
            }
            if (Workspace.Instance.GameStatus == GameStatus.Play)
            //|| Workspace.Instance.GameStatus == GameStatus.Editor)
            {
                GraphicsHelper.CallGameMethod(Game, "Update", new object[] { gameTime });
                GraphicsHelper.CallGameMethod(Game, "Draw", new object[] { gameTime });
            }
        }
    }
}
